Package org.python.pydev.core.docutils

Source Code of org.python.pydev.core.docutils.PySelection$LineStartingScope

/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* @author: ptoofani
* @author Fabio Zadrozny
* Created: June 2004
*/

package org.python.pydev.core.docutils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
import org.python.pydev.core.ICodeCompletionASTManager.ImportInfo;
import org.python.pydev.core.IPythonPartitions;
import org.python.pydev.core.log.Log;

import com.aptana.shared_core.string.FastStringBuffer;
import com.aptana.shared_core.structure.Tuple;
import com.aptana.shared_core.utils.TextSelectionUtils;

/**
* Redone the whole class, so that the interface is better defined and no
* duplication of information is given.
*
* Now, it is just used as 'shortcuts' to document and selection settings.
*
* @author Fabio Zadrozny
* @author Parhaum Toofanian
*/
public final class PySelection extends TextSelectionUtils {

    public static final String[] DEDENT_TOKENS = new String[] { "return", "break", "continue", "pass", "raise",
            //        "yield" -- https://sourceforge.net/tracker/index.php?func=detail&aid=1807411&group_id=85796&atid=577329 (doesn't really end scope)
            //      after seeing the std lib, several cases use yield at the middle of the scope
    };

    public static final String[] CLASS_AND_FUNC_TOKENS = new String[] { "def", "class", };

    public static final String[] CLASS_TOKEN = new String[] { "class", };

    public static final String[] INDENT_TOKENS = new String[] { "if", "for", "except", "def", "class", "else", "elif",
            "while", "try", "with", "finally" };

    public static final Set<String> STATEMENT_TOKENS = new HashSet<String>();
    static {
        //Note that lambda is not here because it's usually inside other statements
        STATEMENT_TOKENS.add("assert");
        STATEMENT_TOKENS.add("break");
        STATEMENT_TOKENS.add("class");
        STATEMENT_TOKENS.add("continue");
        STATEMENT_TOKENS.add("def");
        STATEMENT_TOKENS.add("elif");
        //STATEMENT_TOKENS.add("else"); -- can be used in the construct None if True else ''
        STATEMENT_TOKENS.add("except");
        STATEMENT_TOKENS.add("finally");
        //STATEMENT_TOKENS.add("for"); -- can be used in list comprehensions
        STATEMENT_TOKENS.add("from");
        //STATEMENT_TOKENS.add("if"); -- can be used in the construct None if True else ''
        STATEMENT_TOKENS.add("import");
        STATEMENT_TOKENS.add("pass");
        STATEMENT_TOKENS.add("raise");
        STATEMENT_TOKENS.add("return");
        STATEMENT_TOKENS.add("try");
        STATEMENT_TOKENS.add("while");
        STATEMENT_TOKENS.add("with");
        STATEMENT_TOKENS.add("yield");
    };

    public static final Set<String> ALL_KEYWORD_TOKENS = new HashSet<String>();
    static {
        ALL_KEYWORD_TOKENS.add("False");
        ALL_KEYWORD_TOKENS.add("None");
        ALL_KEYWORD_TOKENS.add("True");
        ALL_KEYWORD_TOKENS.add("and");
        ALL_KEYWORD_TOKENS.add("as");
        ALL_KEYWORD_TOKENS.add("assert");
        ALL_KEYWORD_TOKENS.add("break");
        ALL_KEYWORD_TOKENS.add("class");
        ALL_KEYWORD_TOKENS.add("continue");
        ALL_KEYWORD_TOKENS.add("def");
        ALL_KEYWORD_TOKENS.add("del");
        ALL_KEYWORD_TOKENS.add("elif");
        ALL_KEYWORD_TOKENS.add("else");
        ALL_KEYWORD_TOKENS.add("except");
        ALL_KEYWORD_TOKENS.add("exec");
        ALL_KEYWORD_TOKENS.add("finally");
        ALL_KEYWORD_TOKENS.add("for");
        ALL_KEYWORD_TOKENS.add("from");
        ALL_KEYWORD_TOKENS.add("global");
        ALL_KEYWORD_TOKENS.add("if");
        ALL_KEYWORD_TOKENS.add("import");
        ALL_KEYWORD_TOKENS.add("in");
        ALL_KEYWORD_TOKENS.add("is");
        ALL_KEYWORD_TOKENS.add("lambda");
        ALL_KEYWORD_TOKENS.add("nonlocal");
        ALL_KEYWORD_TOKENS.add("not");
        ALL_KEYWORD_TOKENS.add("or");
        ALL_KEYWORD_TOKENS.add("pass");
        ALL_KEYWORD_TOKENS.add("print");
        ALL_KEYWORD_TOKENS.add("raise");
        ALL_KEYWORD_TOKENS.add("return");
        ALL_KEYWORD_TOKENS.add("self");
        ALL_KEYWORD_TOKENS.add("try");
        ALL_KEYWORD_TOKENS.add("while");
        ALL_KEYWORD_TOKENS.add("with");
        ALL_KEYWORD_TOKENS.add("yield");
    };

    /**
     * Alternate constructor for PySelection. Takes in a text editor from Eclipse.
     *
     * @param textEditor The text editor operating in Eclipse
     */
    public PySelection(ITextEditor textEditor) {
        this(textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()), (ITextSelection) textEditor
                .getSelectionProvider().getSelection());
    }

    /**
     * @param document the document we are using to make the selection
     * @param selection that's the actual selection. It might have an offset and a number of selected chars
     */
    public PySelection(IDocument doc, ITextSelection selection) {
        super(doc, selection);
    }

    /**
     * Creates a selection from a document
     * @param doc the document to be used
     * @param line the line (starts at 0)
     * @param col the col (starts at 0)
     */
    public PySelection(IDocument doc, int line, int col) {
        this(doc, line, col, 0);
    }

    public PySelection(IDocument doc, int line, int col, int len) {
        super(doc, new TextSelection(doc, getAbsoluteCursorOffset(doc, line, col), len));
    }

    public static int getAbsoluteCursorOffset(IDocument doc, int line, int col) {
        try {
            IRegion offsetR = doc.getLineInformation(line);
            return offsetR.getOffset() + col;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param line 0-based
     * @param col 0-based
     * @return the absolute cursor offset in the contained document
     */
    public int getAbsoluteCursorOffset(int line, int col) {
        return getAbsoluteCursorOffset(doc, line, col);
    }

    /**
     * @param document the document we are using to make the selection
     * @param offset the offset where the selection will happen (0 characters will be selected)
     */
    public PySelection(IDocument doc, int offset) {
        super(doc, new TextSelection(doc, offset, 0));
    }

    /**
     * Changes the selection
     * @param absoluteStart this is the offset of the start of the selection
     * @param absoluteEnd this is the offset of the end of the selection
     */
    public void setSelection(int absoluteStart, int absoluteEnd) {
        this.textSelection = new TextSelection(doc, absoluteStart, absoluteEnd - absoluteStart);
    }

    /**
     * Creates a selection for the document, so that no characters are selected and the offset is position 0
     * @param doc the document where we are doing the selection
     */
    public PySelection(IDocument doc) {
        this(doc, 0);
    }

    /**
     * Creates a selection based on another selection.
     */
    public PySelection(PySelection base) {
        super(base.doc, new TextSelection(base.doc, base.getAbsoluteCursorOffset(), base.getSelLength()));
    }

    /**
     * In event of partial selection, used to select the full lines involved.
     */
    public void selectCompleteLine() {
        IRegion endLine = getEndLine();
        IRegion startLine = getStartLine();

        this.textSelection = new TextSelection(doc, startLine.getOffset(), endLine.getOffset() + endLine.getLength()
                - startLine.getOffset());
    }

    /**
     * @return true if the passed line has a from __future__ import.
     */
    public static boolean isFutureImportLine(String line) {
        List<String> split = StringUtils.split(line, ' ', '\t');
        int fromIndex = split.indexOf("from");
        int futureIndex = split.indexOf("__future__");
        boolean isFuture = fromIndex != -1 && futureIndex != -1 && futureIndex == fromIndex + 1;
        return isFuture;
    }

    /**
     * @param trimmedLine a line that's already trimmed!
     * @return true if it seems the current line is an import line (i.e.: starts with 'import' or 'from')
     */
    public static boolean isImportLine(String trimmedLine) {
        List<String> split = StringUtils.split(trimmedLine, ' ', '\t');
        if (split.size() == 0) { //nothing to see her
            return false;
        }
        String pos0 = split.get(0);
        return pos0.equals("import") || pos0.equals("from");
    }

    /**
     * @param isFutureImport if true, that means that the location found must match a from __future__ import (which
     * must be always put as the 1st import)
     * 
     * @return the line where a global import would be able to happen.
     *
     * The 'usual' structure that we take into consideration for a py file here is:
     *
     * #coding ...
     *
     * '''
     * multiline comment...
     * '''
     *
     * imports #that's what we want to find out
     *
     * code
     */
    public int getLineAvailableForImport(boolean isFutureImport) {
        FastStringBuffer multiLineBuf = new FastStringBuffer();
        int[] firstGlobalLiteral = getFirstGlobalLiteral(multiLineBuf, 0);

        if (multiLineBuf.length() > 0 && firstGlobalLiteral[0] >= 0 && firstGlobalLiteral[1] >= 0) {
            //ok, multiline found
            int startingMultilineComment = getLineOfOffset(firstGlobalLiteral[0]);

            if (startingMultilineComment < 4) {

                //let's see if the multiline comment found is in the beginning of the document
                int lineOfOffset = getLineOfOffset(firstGlobalLiteral[1]);
                return getLineAvailableForImport(lineOfOffset + 1, isFutureImport);
            } else {

                return getLineAvailableForImport(0, isFutureImport);
            }
        } else {

            //ok, no multiline comment, let's get the first line that is not a comment
            return getLineAvailableForImport(0, isFutureImport);
        }
    }

    /**
     * @return the first line found that is not a comment.
     */
    private int getLineAvailableForImport(int startingAtLine, boolean isFutureImport) {
        int firstNonCommentLine = -1;
        int afterFirstImports = -1;

        IDocument document = getDoc();
        int lines = document.getNumberOfLines();
        ParsingUtils parsingUtils = ParsingUtils.create(document);
        for (int line = startingAtLine; line < lines; line++) {
            String str = getLine(line);
            if (str.trim().startsWith("__version__")) {
                continue;
            }
            if (str.startsWith("#")) {
                continue;
            } else {
                int i;
                if ((i = str.indexOf('#')) != -1) {
                    str = str.substring(0, i);
                }

                if (firstNonCommentLine == -1) {
                    firstNonCommentLine = line;
                }
                ImportInfo importInfo = ImportsSelection.getImportsTipperStr(str, false);
                //Don't check with trim (importInfo.importsTipperStr.trim().length()) because the string
                //will be " " in an import without a 'from'
                if (importInfo != null && importInfo.importsTipperStr != null
                        && importInfo.importsTipperStr.length() > 0) {
                    if ((i = str.indexOf('(')) != -1) {
                        //start of a multiline import
                        int lineOffset = -1;
                        try {
                            lineOffset = document.getLineOffset(line);
                        } catch (BadLocationException e1) {
                            throw new RuntimeException(e1);
                        }
                        int j;
                        try {
                            j = parsingUtils.eatPar(lineOffset + i, null);
                        } catch (SyntaxErrorException e1) {
                            throw new RuntimeException(e1);
                        }
                        try {
                            line = document.getLineOfOffset(j);
                        } catch (BadLocationException e) {
                            Log.log(e);
                        }
                    } else if (str.endsWith("\\")) {
                        while (str.endsWith("\\") && line < lines) {
                            line++;
                            str = getLine(line);
                        }
                    }
                    afterFirstImports = line + 1;
                } else if (str.trim().length() > 0) {
                    //found some non-empty, non-import, non-comment line (break it here)
                    break;
                }
            }
        }
        if (isFutureImport) {
            return firstNonCommentLine;
        }
        return afterFirstImports > firstNonCommentLine ? afterFirstImports : firstNonCommentLine;
    }

    /**
     * @param initialOffset this is the offset we should use to analyze it
     * @param buf (out) this is the comment itself
     * @return a tuple with the offset of the start and end of the first multiline comment found
     */
    public int[] getFirstGlobalLiteral(FastStringBuffer buf, int initialOffset) {
        try {
            IDocument d = getDoc();
            String strDoc = d.get(initialOffset, d.getLength() - initialOffset);

            int docLen = strDoc.length();

            if (initialOffset > docLen - 1) {
                return new int[] { -1, -1 };
            }

            char current = strDoc.charAt(initialOffset);
            ParsingUtils parsingUtils = ParsingUtils.create(strDoc);
            //for checking if it is global, it must be in the beggining of a line (must be right after a \r or \n).

            while (current != '\'' && current != '"' && initialOffset < docLen - 1) {

                //if it is inside a parenthesis, we will not take it into consideration.
                if (current == '(') {
                    initialOffset = parsingUtils.eatPar(initialOffset, buf);
                }

                initialOffset += 1;
                if (initialOffset < docLen - 1) {
                    current = strDoc.charAt(initialOffset);
                }
            }

            //either, we are at the end of the document or we found a literal
            if (initialOffset < docLen - 1) {

                if (initialOffset == 0) { //first char of the document... this is ok
                    int i = parsingUtils.eatLiterals(buf, initialOffset);
                    return new int[] { initialOffset, i };
                }

                char lastChar = strDoc.charAt(initialOffset - 1);
                //it is only global if after \r or \n
                if (lastChar == '\r' || lastChar == '\n') {
                    int i = parsingUtils.eatLiterals(buf, initialOffset);
                    return new int[] { initialOffset, i };
                }

                //ok, still not found, let's keep going
                return getFirstGlobalLiteral(buf, initialOffset + 1);
            } else {
                return new int[] { -1, -1 };

            }
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        } catch (SyntaxErrorException e) {
            throw new RuntimeException(e);
        }
    }

    protected static void beep(Exception e) {
        Log.log(e);
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell().getDisplay().beep();
    }

    public static String getLineWithoutCommentsOrLiterals(String l) {
        FastStringBuffer buf = new FastStringBuffer(l, 2);
        boolean throwSyntaxError = false;
        try {
            ParsingUtils.removeCommentsWhitespacesAndLiterals(buf, false, throwSyntaxError);
        } catch (SyntaxErrorException e) {
            throw new RuntimeException(e);
        }
        return buf.toString();

    }

    public String getLineWithoutCommentsOrLiterals() {
        return getLineWithoutCommentsOrLiterals(getLine());
    }

    public static String getLineWithoutLiterals(String line) {
        FastStringBuffer buf = new FastStringBuffer(line, 2);
        boolean throwSyntaxError = false;
        try {
            ParsingUtils.removeLiterals(buf, throwSyntaxError);
        } catch (SyntaxErrorException e) {
            throw new RuntimeException(e);
        }
        return buf.toString();
    }

    /**
     * @return the current column that is selected from the cursor.
     */
    public int getCursorColumn() {
        try {
            int absoluteOffset = getAbsoluteCursorOffset();
            IRegion region = doc.getLineInformationOfOffset(absoluteOffset);
            return absoluteOffset - region.getOffset();
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Gets current line from document.
     *
     * @return String line in String form
     */
    public String getLine() {
        return getLine(getDoc(), getCursorLine());
    }

    /**
     * Gets line from document.
     *
     * @param i Line number
     * @return String line in String form
     */
    public String getLine(int i) {
        return getLine(getDoc(), i);
    }

    /**
     * Gets line from document.
     *
     * @param i Line number
     * @return String line in String form
     */
    public static String getLine(IDocument doc, int i) {
        try {
            IRegion lineInformation = doc.getLineInformation(i);
            return doc.get(lineInformation.getOffset(), lineInformation.getLength());
        } catch (Exception e) {
            return "";
        }
    }

    public int getLineOfOffset() {
        return getLineOfOffset(this.getAbsoluteCursorOffset());
    }

    public int getLineOfOffset(int offset) {
        return getLineOfOffset(getDoc(), offset);
    }

    /**
     * @param offset the offset we want to get the line
     * @return the line of the passed offset
     */
    public static int getLineOfOffset(IDocument doc, int offset) {
        try {
            return doc.getLineOfOffset(offset);
        } catch (BadLocationException e) {
            if (offset > doc.getLength() - 1) {
                int numberOfLines = doc.getNumberOfLines();
                if (numberOfLines == 0) {
                    return 0;
                }
                return numberOfLines - 1;
            }
            return 0;
        }
    }

    /**
     * Deletes a line from the document
     * @param i
     */
    public void deleteLine(int i) {
        deleteLine(getDoc(), i);
    }

    /**
     * Deletes a line from the document
     * @param i
     */
    public static void deleteLine(IDocument doc, int i) {
        try {
            IRegion lineInformation = doc.getLineInformation(i);
            int offset = lineInformation.getOffset();

            int length = -1;

            if (doc.getNumberOfLines() > i) {
                int nextLineOffset = doc.getLineInformation(i + 1).getOffset();
                length = nextLineOffset - offset;
            } else {
                length = lineInformation.getLength();
            }

            if (length > -1) {
                doc.replace(offset, length, "");
            }
        } catch (BadLocationException e) {
            Log.log(e);
        }
    }

    public void deleteSpacesAfter(int offset) {
        try {
            final int len = countSpacesAfter(offset);
            if (len > 0) {
                doc.replace(offset, len, "");
            }
        } catch (Exception e) {
            //ignore
        }
    }

    public int countSpacesAfter(int offset) throws BadLocationException {
        if (offset >= doc.getLength()) {
            return 0;
        }

        int initial = offset;
        String next = doc.get(offset, 1);

        //don't delete 'all' that is considered whitespace (as \n and \r)
        try {
            while (next.charAt(0) == ' ' || next.charAt(0) == '\t') {
                offset++;
                next = doc.get(offset, 1);
            }
        } catch (Exception e) {
            // ignore
        }

        return offset - initial;
    }

    /**
     * Deletes the current selected text
     *
     * @throws BadLocationException
     */
    public void deleteSelection() throws BadLocationException {
        int offset = textSelection.getOffset();
        doc.replace(offset, textSelection.getLength(), "");
    }

    public void addLine(String contents, int afterLine) {
        addLine(getDoc(), getEndLineDelim(), contents, afterLine);
    }

    /**
     * Adds a line to the document.
     *
     * @param doc the document
     * @param endLineDelim the delimiter that should be used
     * @param contents what should be added (the end line delimiter may be added before or after those contents
     *  (depending on what are the current contents of the document).
     * @param afterLine the contents should be added after the line specified here.
     */
    public static void addLine(IDocument doc, String endLineDelim, String contents, int afterLine) {
        try {

            int offset = -1;
            if (doc.getNumberOfLines() > afterLine) {
                offset = doc.getLineInformation(afterLine + 1).getOffset();

            } else {
                offset = doc.getLineInformation(afterLine).getOffset();
            }

            if (doc.getNumberOfLines() - 1 == afterLine) {
                contents = endLineDelim + contents;

            }

            if (!contents.endsWith(endLineDelim)) {
                contents += endLineDelim;
            }

            if (offset >= 0) {
                doc.replace(offset, 0, contents);
            }
        } catch (BadLocationException e) {
            Log.log(e);
        }
    }

    public String getLineContentsFromCursor() throws BadLocationException {
        return getLineContentsFromCursor(getAbsoluteCursorOffset());
    }

    /**
     * @return the line where the cursor is (from the cursor position to the end of the line).
     * @throws BadLocationException
     */
    public String getLineContentsFromCursor(int offset) throws BadLocationException {
        int lineOfOffset = doc.getLineOfOffset(offset);
        IRegion lineInformation = doc.getLineInformation(lineOfOffset);

        String lineToCursor = doc.get(offset, lineInformation.getOffset() + lineInformation.getLength() - offset);
        return lineToCursor;
    }

    /**
     * Get the current line up to where the cursor is without any comments or literals.
     */
    public String getLineContentsToCursor(boolean removeComments, boolean removeLiterals) throws BadLocationException {
        if (removeComments == false || removeLiterals == false) {
            throw new RuntimeException("Currently only accepts removing the literals and comments.");
        }
        int cursorOffset = getAbsoluteCursorOffset();

        IRegion lineInformationOfOffset = doc.getLineInformationOfOffset(cursorOffset);
        IDocumentPartitioner partitioner = PyPartitionScanner.checkPartitionScanner(doc);
        if (partitioner == null) {
            throw new RuntimeException("Partitioner not set up.");
        }

        StringBuffer buffer = new StringBuffer();
        int offset = lineInformationOfOffset.getOffset();
        int length = lineInformationOfOffset.getLength();
        for (int i = offset; i <= offset + length && i < cursorOffset; i++) {
            String contentType = partitioner.getContentType(i);
            if (contentType.equals(IPythonPartitions.PY_DEFAULT)) {
                buffer.append(doc.getChar(i));
            } else {
                buffer.append(' ');
            }
        }
        return buffer.toString();
    }

    /**
     * @param ps
     * @return the line where the cursor is (from the beggining of the line to the cursor position).
     * @throws BadLocationException
     */
    public String getLineContentsToCursor() throws BadLocationException {
        int offset = getAbsoluteCursorOffset();
        return getLineContentsToCursor(offset);
    }

    public String getLineContentsToCursor(int offset) throws BadLocationException {
        int lineOfOffset = doc.getLineOfOffset(offset);
        IRegion lineInformation = doc.getLineInformation(lineOfOffset);
        String lineToCursor = doc.get(lineInformation.getOffset(), offset - lineInformation.getOffset());
        return lineToCursor;
    }

    /**
     * Readjust the selection so that the whole document is selected.
     *
     * @param onlyIfNothingSelected: If false, check if we already have a selection. If we
     * have a selection, it is not changed, however, if it is true, it always selects everything.
     */
    public void selectAll(boolean forceNewSelection) {
        if (!forceNewSelection) {
            if (getSelLength() > 0)
                return;
        }

        textSelection = new TextSelection(doc, 0, doc.getLength());
    }

    /**
     * @return Returns the startLineIndex.
     */
    public int getStartLineIndex() {
        return this.getTextSelection().getStartLine();
    }

    /**
     * @return Returns the selLength.
     */
    public int getSelLength() {
        return this.getTextSelection().getLength();
    }

    /**
     * @return Returns the selection.
     */
    public String getCursorLineContents() {
        try {
            IRegion startLine = getStartLine();
            if (startLine == null) {
                return "";
            }
            int start = startLine.getOffset();
            IRegion endLine = getEndLine();
            if (endLine == null) {
                return "";
            }
            int end = endLine.getOffset() + endLine.getLength();
            return this.doc.get(start, end - start);
        } catch (BadLocationException e) {
            Log.log(e);
        }
        return "";
    }

    /**
     * @return the delimiter that should be used for the passed document
     */
    public static String getDelimiter(IDocument doc) {
        return TextUtilities.getDefaultLineDelimiter(doc);
    }

    /**
     * @return Returns the endLineDelim.
     */
    public String getEndLineDelim() {
        return getDelimiter(getDoc());
    }

    /**
     * @return Returns the startLine.
     */
    public IRegion getStartLine() {
        try {
            return getDoc().getLineInformation(getStartLineIndex());
        } catch (BadLocationException e) {
            Log.log(e);
        }
        return null;
    }

    /**
     * @return the Selected text
     */
    public String getSelectedText() {
        ITextSelection txtSel = getTextSelection();
        int start = txtSel.getOffset();
        int len = txtSel.getLength();
        try {
            return this.doc.get(start, len);
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @return
     * @throws BadLocationException
     */
    public char getCharAfterCurrentOffset() throws BadLocationException {
        return getDoc().getChar(getAbsoluteCursorOffset() + 1);
    }

    /**
     * @return
     * @throws BadLocationException
     */
    public char getCharAtCurrentOffset() throws BadLocationException {
        return getDoc().getChar(getAbsoluteCursorOffset());
    }

    public Tuple<String, String> getBeforeAndAfterMatchingChars(char c) {
        final int initial = getAbsoluteCursorOffset();
        int curr = initial - 1;
        IDocument doc = getDoc();
        FastStringBuffer buf = new FastStringBuffer(10);
        int length = doc.getLength();

        while (curr >= 0 && curr < length) {
            char gotten;
            try {
                gotten = doc.getChar(curr);
            } catch (BadLocationException e) {
                break;
            }
            if (gotten == c) {
                buf.append(c);
            } else {
                break;
            }
            curr--;
        }
        String before = buf.toString();
        buf.clear();
        curr = initial;

        while (curr >= 0 && curr < length) {
            char gotten;
            try {
                gotten = doc.getChar(curr);
            } catch (BadLocationException e) {
                break;
            }
            if (gotten == c) {
                buf.append(c);
            } else {
                break;
            }
            curr++;
        }
        String after = buf.toString();
        return new Tuple<String, String>(before, after);
    }

    /**
     * @return the offset mapping to the start of the current line.
     */
    public int getStartLineOffset() {
        IRegion startLine = getStartLine();
        return startLine.getOffset();
    }

    /**
     * @return the complete dotted string given the current selection and the strings after
     *
     * e.g.: if we have a text of
     * 'value = aa.bb.cc()' and 'aa' is selected, this method would return the whole dotted string ('aa.bb.cc')
     * @throws BadLocationException
     */
    public String getFullRepAfterSelection() throws BadLocationException {
        int absoluteCursorOffset = getAbsoluteCursorOffset();
        int length = doc.getLength();
        int end = absoluteCursorOffset;
        char ch = doc.getChar(end);
        while (Character.isLetterOrDigit(ch) || ch == '.') {
            end++;
            //check if we can still get some char
            if (length - 1 < end) {
                break;
            }
            ch = doc.getChar(end);
        }
        return doc.get(absoluteCursorOffset, end - absoluteCursorOffset);
    }

    public Tuple<String, Integer> getCurrDottedStatement() throws BadLocationException {
        int absoluteCursorOffset = getAbsoluteCursorOffset();
        int start = absoluteCursorOffset;
        for (int i = absoluteCursorOffset - 1; i >= 0; i--) {
            char c = doc.getChar(i);
            if (!Character.isJavaIdentifierPart(c) && c != '.') {
                //We're at the start now, so, let's go onwards now...
                if (StringUtils.isClosingPeer(c)) {
                    int j = new PythonPairMatcher().searchForOpeningPeer(i, StringUtils.getPeer(c), c, doc);
                    if (j < 0) {
                        break;
                    }
                    i = j;
                } else {
                    break;
                }
            }
            start = i;
        }

        int len = doc.getLength();
        int end = absoluteCursorOffset;
        for (int i = absoluteCursorOffset; i < len; i++) {
            char c = doc.getChar(i);
            if (!Character.isJavaIdentifierPart(c) && c != '.') {
                if (StringUtils.isOpeningPeer(c)) {
                    int j = new PythonPairMatcher().searchForClosingPeer(i, c, StringUtils.getPeer(c), doc);
                    if (j < 0) {
                        break;
                    }
                    i = j;
                } else {
                    break;
                }
            }
            end = i + 1;
        }

        if (start != end) {
            return new Tuple<String, Integer>(doc.get(start, end - start), start);
        }

        return new Tuple<String, Integer>("", absoluteCursorOffset);
    }

    /**
     * @return the current token and its initial offset for this token
     * @throws BadLocationException
     */
    public Tuple<String, Integer> getCurrToken() throws BadLocationException {
        Tuple<String, Integer> tup = extractActivationToken(doc, getAbsoluteCursorOffset(), false);
        String prefix = tup.o1;

        // ok, now, get the rest of the token, as we already have its prefix

        int start = tup.o2 - prefix.length();
        int end = start;
        while (doc.getLength() - 1 >= end) {
            char ch = doc.getChar(end);
            if (Character.isJavaIdentifierPart(ch)) {
                end++;
            } else {
                break;
            }
        }
        String post = doc.get(tup.o2, end - tup.o2);
        return new Tuple<String, Integer>(prefix + post, start);
    }

    public Tuple<List<String>, Integer> getInsideParentesisToks(boolean addSelf) {
        String line = getLine();
        int openParIndex = line.indexOf('(');
        if (openParIndex <= -1) { // we are in a line that does not have a parenthesis
            return null;
        }

        int lineOffset = getStartLineOffset();
        int i = lineOffset + openParIndex;

        return getInsideParentesisToks(addSelf, i, false);
    }

    public Tuple<List<String>, Integer> getInsideParentesisToks(boolean addSelf, int iLine) {
        String line = getLine(iLine);
        int openParIndex = line.indexOf('(');
        if (openParIndex <= -1) { // we are in a line that does not have a parenthesis
            return null;
        }

        int lineOffset = getLineOffset(iLine);
        int i = lineOffset + openParIndex;

        return getInsideParentesisToks(addSelf, i, false);
    }

    /**
     * This function gets the tokens inside the parenthesis that start at the current selection line
     *
     * @param addSelf: this defines whether tokens named self should be added if it is found.
     *
     * @param isCall: if it's a call, when we have in the parenthesis something as Call(a, (b,c)), it'll return
     * in the list as items:
     *
     * a
     * (b,c)
     *
     * Otherwise (in a definition), it'll return
     *
     * a
     * b
     * c
     *
     * @return a Tuple so that the first param is the list and the second the offset of the end of the parenthesis.
     * It may return null if no starting parenthesis was found at the current line
     */
    public Tuple<List<String>, Integer> getInsideParentesisToks(boolean addSelf, int offset, boolean isCall) {
        List<String> params = new ArrayList<String>();
        String docContents = doc.get();
        int j;
        try {
            if (isCall) {
                ParsingUtils parsingUtils = ParsingUtils.create(docContents);
                j = parsingUtils.eatPar(offset, null);
                final String insideParentesisTok = docContents.substring(offset + 1, j);
                final ParsingUtils insideParensParsingUtils = ParsingUtils.create(insideParentesisTok);
                final int len = insideParentesisTok.length();
                final FastStringBuffer buf = new FastStringBuffer(len);

                for (int i = 0; i < len; i++) {
                    char c = insideParentesisTok.charAt(i);
                    if (c == ',') {
                        String trim = buf.toString().trim();
                        if (trim.length() > 0) {
                            params.add(trim);
                        }
                        buf.clear();
                    } else {
                        switch (c) {
                            case '\'':
                            case '"':
                                j = insideParensParsingUtils.eatLiterals(null, i);
                                buf.append(insideParentesisTok.substring(i, j + 1));
                                i = j;
                                break;

                            case '{':
                            case '(':
                            case '[':
                                j = insideParensParsingUtils.eatPar(i, null, c);
                                buf.append(insideParentesisTok.substring(i, j + 1));
                                i = j;
                                break;

                            default:
                                buf.append(c);
                        }
                    }
                }
                String trim = buf.toString().trim();
                if (trim.length() > 0) {
                    params.add(trim);
                }

            } else {
                ParsingUtils parsingUtils = ParsingUtils.create(docContents);
                final FastStringBuffer buf = new FastStringBuffer();
                j = parsingUtils.eatPar(offset, buf);

                final String insideParentesisTok = buf.toString();

                StringTokenizer tokenizer = new StringTokenizer(insideParentesisTok, ",");
                while (tokenizer.hasMoreTokens()) {
                    String tok = tokenizer.nextToken();
                    String trimmed = tok.split("=")[0].trim();
                    trimmed = trimmed.replaceAll("\\(", "");
                    trimmed = trimmed.replaceAll("\\)", "");
                    if (!addSelf && trimmed.equals("self")) {
                        // don't add self...
                    } else if (trimmed.length() > 0) {
                        int colonPos;
                        if ((colonPos = trimmed.indexOf(':')) != -1) {
                            trimmed = trimmed.substring(0, colonPos);
                            trimmed = trimmed.trim();
                        }
                        if (trimmed.length() > 0) {
                            params.add(trimmed);
                        }
                    }
                }
            }
        } catch (SyntaxErrorException e) {
            throw new RuntimeException(e);

        }
        return new Tuple<List<String>, Integer>(params, j);
    }

    /**
     * This function replaces all the contents in the current line before the cursor for the contents passed
     * as parameter
     */
    public void replaceLineContentsToSelection(String newContents) throws BadLocationException {
        int lineOfOffset = getDoc().getLineOfOffset(getAbsoluteCursorOffset());
        IRegion lineInformation = getDoc().getLineInformation(lineOfOffset);
        getDoc().replace(lineInformation.getOffset(), getAbsoluteCursorOffset() - lineInformation.getOffset(),
                newContents);

    }

    public static final String[] TOKENS_BEFORE_ELSE = new String[] { "if", "for", "except", "while", "elif" };

    public static final String[] TOKENS_BEFORE_ELIF = new String[] { "if", "elif" };

    public static final String[] TOKENS_BEFORE_EXCEPT = new String[] { "try" };

    public static final String[] TOKENS_BEFORE_FINALLY = new String[] { "try", "except" };

    /**
     * This function goes backward in the document searching for an 'if' and returns the line that has it.
     *
     * May return null if it was not found.
     */
    public String getPreviousLineThatStartsWithToken(String[] tokens) {
        DocIterator iterator = new DocIterator(false, this, this.getCursorLine() - 1, false);
        FastStringBuffer buf = new FastStringBuffer();

        HashSet<Character> initials = new HashSet<Character>();
        for (String t : tokens) {
            if (t.length() > 0) {
                initials.add(t.charAt(0));
            }
        }

        int indentMustBeHigherThan = -1;
        int currLineIndent = -1;
        int skipLinesHigherThan = Integer.MAX_VALUE;

        while (iterator.hasNext()) {
            String line = (String) iterator.next();
            String trimmed = line.trim();
            int len = trimmed.length();
            int lastReturnedLine = iterator.getLastReturnedLine();
            if (lastReturnedLine > skipLinesHigherThan) {
                continue;
            }

            if (len > 0) {
                //Fast way out of a line...
                char c0 = trimmed.charAt(0);

                if (currLineIndent == 0) {
                    //actually, at this point it's from the previous line...

                    //If the indent expected is == 0, if the indent wasn't found on the first match, it's not possible
                    //to get a lower match!
                    return null;
                }
                currLineIndent = getFirstCharPosition(line);
                if (indentMustBeHigherThan == -1) {
                    if (c0 != '#') {
                        //ignore only-comment lines...
                        boolean validIndentLine = true;
                        Tuple<Character, Integer> found = null;
                        for (char c : StringUtils.CLOSING_BRACKETS) {
                            int i = line.lastIndexOf(c);
                            if (found == null || found.o2 < i) {
                                found = new Tuple<Character, Integer>(c, i);
                            }
                        }
                        if (found != null) {
                            PythonPairMatcher matcher = new PythonPairMatcher();
                            int openingPeerOffset = matcher.searchForOpeningPeer(this.getLineOffset(lastReturnedLine)
                                    + found.o2, StringUtils.getPeer(found.o1), found.o1, this.getDoc());
                            if (openingPeerOffset >= 0) {
                                int lineOfOffset = getLineOfOffset(openingPeerOffset);
                                if (lineOfOffset != lastReturnedLine) {
                                    skipLinesHigherThan = lineOfOffset;
                                    validIndentLine = false;
                                }
                            }
                        }

                        if (validIndentLine) {
                            indentMustBeHigherThan = currLineIndent;
                        } else {
                            currLineIndent = -1;
                            continue;
                        }
                    }

                } else {
                    if (indentMustBeHigherThan <= currLineIndent) {
                        continue;
                    }
                }

                if (!initials.contains(c0)) {
                    continue;
                }

                buf.clear();
                buf.append(c0);
            }

            for (int i = 1; i < len; i++) {
                char c = trimmed.charAt(i);
                if (Character.isJavaIdentifierPart(c)) {
                    buf.append(c);
                } else {
                    break;
                }
            }
            String firstWord = buf.toString();
            for (String prefix : tokens) {
                if (firstWord.equals(prefix)) {
                    return line;
                }
            }
        }
        return null;
    }

    public LineStartingScope getPreviousLineThatStartsScope() {
        return getPreviousLineThatStartsScope(PySelection.INDENT_TOKENS, true, Integer.MAX_VALUE);
    }

    public LineStartingScope getNextLineThatStartsScope() {
        return getNextLineThatStartsScope(PySelection.INDENT_TOKENS, true, Integer.MAX_VALUE);
    }

    public LineStartingScope getPreviousLineThatStartsScope(String[] indentTokens, boolean considerCurrentLine,
            int mustHaveIndentLowerThan) {
        int lineToStart = -1;
        if (!considerCurrentLine) {
            lineToStart = getCursorLine() - 1;
        }
        return getPreviousLineThatStartsScope(indentTokens, lineToStart, mustHaveIndentLowerThan);
    }

    public LineStartingScope getNextLineThatStartsScope(String[] indentTokens, boolean considerCurrentLine,
            int mustHaveIndentLowerThan) {
        int lineToStart = -1;
        if (!considerCurrentLine) {
            lineToStart = getCursorLine() - 1;
        }
        return getNextLineThatStartsScope(indentTokens, lineToStart, mustHaveIndentLowerThan);
    }

    public LineStartingScope getPreviousLineThatStartsScope(int lineToStart) {
        return getPreviousLineThatStartsScope(PySelection.INDENT_TOKENS, lineToStart, Integer.MAX_VALUE);
    }

    public static class LineStartingScope {

        public final String lineStartingScope;
        public final String lineWithDedentWhileLookingScope;
        public final String lineWithLowestIndent;
        public final int iLineStartingScope;

        public LineStartingScope(String lineStartingScope, String lineWithDedentWhileLookingScope,
                String lineWithLowestIndent, int iLineStartingScope) {
            this.lineStartingScope = lineStartingScope;
            this.lineWithDedentWhileLookingScope = lineWithDedentWhileLookingScope;
            this.lineWithLowestIndent = lineWithLowestIndent;
            this.iLineStartingScope = iLineStartingScope;
        }
    }

    public LineStartingScope getNextLineThatStartsScope(String[] indentTokens, int lineToStart,
            int mustHaveIndentLowerThan) {
        return getLineThatStartsScope(true, indentTokens, lineToStart, mustHaveIndentLowerThan);
    }

    public LineStartingScope getPreviousLineThatStartsScope(String[] indentTokens, int lineToStart,
            int mustHaveIndentLowerThan) {
        return getLineThatStartsScope(false, indentTokens, lineToStart, mustHaveIndentLowerThan);
    }

    /**
     * @param lineToStart: if -1, it'll start at the current line.
     *
     * @return a tuple with:
     * - the line that starts the new scope
     * - a String with the line where some dedent token was found while looking for that scope.
     * - a string with the lowest indent (null if none was found)
     */
    public LineStartingScope getLineThatStartsScope(boolean forward, String[] indentTokens, int lineToStart,
            int mustHaveIndentLowerThan) {
        final DocIterator iterator;
        if (lineToStart == -1) {
            iterator = new DocIterator(forward, this);
        } else {
            iterator = new DocIterator(forward, this, lineToStart, false);
        }

        String foundDedent = null;
        String lowestStr = null;

        while (iterator.hasNext()) {
            if (mustHaveIndentLowerThan == 0) {
                return null; //we won't find any indent lower than that.
            }
            String line = (String) iterator.next();
            String trimmed = line.trim();

            if (trimmed.startsWith("#")) {
                continue;
            }

            for (String dedent : indentTokens) {
                if (trimmed.startsWith(dedent)) {
                    if (isCompleteToken(trimmed, dedent)) {
                        if (PySelection.getFirstCharPosition(line) < mustHaveIndentLowerThan) {
                            return new LineStartingScope(line, foundDedent, lowestStr, iterator.getLastReturnedLine());
                        } else {
                            break; //we won't find any other because the indent is already wrong.
                        }
                    }
                }
            }
            //we have to check for the first condition (if a dedent is found, but we already found
            //one with a first char, the dedent should not be taken into consideration... and vice-versa).
            if (lowestStr == null && foundDedent == null && startsWithDedentToken(trimmed)) {
                foundDedent = line;

            } else if (foundDedent == null && trimmed.length() > 0) {
                int firstCharPosition = getFirstCharPosition(line);
                if (firstCharPosition < mustHaveIndentLowerThan) {
                    mustHaveIndentLowerThan = firstCharPosition;
                    lowestStr = line;
                }
            }

        }
        return null;
    }

    /**
     * @param theDoc
     * @param documentOffset
     * @return
     * @throws BadLocationException
     */
    public static int eatFuncCall(IDocument theDoc, int documentOffset) throws BadLocationException {
        String c = theDoc.get(documentOffset, 1);
        if (c.equals(")") == false) {
            throw new AssertionError("Expecting ) to eat callable. Received: " + c);
        }

        while (documentOffset > 0 && theDoc.get(documentOffset, 1).equals("(") == false) {
            documentOffset -= 1;
        }

        return documentOffset;
    }

    /**
     * Checks if the activationToken ends with some char from cs.
     */
    public static boolean endsWithSomeChar(char cs[], String activationToken) {
        for (int i = 0; i < cs.length; i++) {
            if (activationToken.endsWith(cs[i] + "")) {
                return true;
            }
        }
        return false;

    }

    public static List<Integer> getLineStartOffsets(String replacementString) {
        ArrayList<Integer> ret = new ArrayList<Integer>();
        ret.add(0);//there is always a starting one at 0

        //we may have line breaks with \r\n, or only \n or \r
        for (int i = 0; i < replacementString.length(); i++) {
            char c = replacementString.charAt(i);
            if (c == '\r') {
                i++;
                int foundAt = i;

                if (i < replacementString.length()) {
                    c = replacementString.charAt(i);
                    if (c == '\n') {
                        //                        i++;
                        foundAt = i + 1;
                    }
                }
                ret.add(foundAt);

            } else if (c == '\n') {
                ret.add(i + 1);
            }
        }

        return ret;
    }

    public static List<Integer> getLineBreakOffsets(String replacementString) {
        ArrayList<Integer> ret = new ArrayList<Integer>();

        int lineBreaks = 0;
        int ignoreNextNAt = -1;

        //we may have line breaks with \r\n, or only \n or \r
        for (int i = 0; i < replacementString.length(); i++) {
            char c = replacementString.charAt(i);
            if (c == '\r') {
                lineBreaks++;
                ret.add(i);
                ignoreNextNAt = i + 1;

            } else if (c == '\n') {
                if (ignoreNextNAt != i) {
                    ret.add(i);
                    lineBreaks++;
                }
            }
        }

        return ret;
    }

    public static class ActivationTokenAndQual {
        public ActivationTokenAndQual(String activationToken, String qualifier, boolean changedForCalltip,
                boolean alreadyHasParams, boolean isInMethodKeywordParam, int offsetForKeywordParam, int calltipOffset) {
            this.activationToken = activationToken;
            this.qualifier = qualifier;
            this.changedForCalltip = changedForCalltip;
            this.alreadyHasParams = alreadyHasParams;
            this.isInMethodKeywordParam = isInMethodKeywordParam;
            this.offsetForKeywordParam = offsetForKeywordParam;
            this.calltipOffset = calltipOffset;
        }

        public final String activationToken;
        public final String qualifier;
        public final boolean changedForCalltip;
        public final boolean alreadyHasParams;
        public final boolean isInMethodKeywordParam;
        public final int offsetForKeywordParam; //only set when isInMethodKeywordParam == true
        public final int calltipOffset; //this is where the parameters start
    }

    /**
     * Shortcut
     */
    public String[] getActivationTokenAndQual(boolean getFullQualifier) {
        return getActivationTokenAndQual(doc, getAbsoluteCursorOffset(), getFullQualifier);
    }

    /**
     * Shortcut
     */
    public ActivationTokenAndQual getActivationTokenAndQual(boolean getFullQualifier, boolean handleForCalltips) {
        return getActivationTokenAndQual(doc, getAbsoluteCursorOffset(), getFullQualifier, handleForCalltips);
    }

    /**
     * Shortcut
     */
    public static String[] getActivationTokenAndQual(IDocument theDoc, int documentOffset, boolean getFullQualifier) {
        ActivationTokenAndQual ret = getActivationTokenAndQual(theDoc, documentOffset, getFullQualifier, false);
        return new String[] { ret.activationToken, ret.qualifier }; //will never be changed for the calltip, as we didn't request it
    }

    /**
     * Returns the activation token.
     *
     * @param documentOffset the current cursor offset (we may have to change it if getFullQualifier is true)
     * @param handleForCalltips if true, it will take into account that we may be looking for the activation token and
     * qualifier for a calltip, in which case we should return the activation token and qualifier before a parenthesis (if we're
     * just after a '(' or ',' ).
     *
     * @return the activation token and the qualifier.
     */
    public static ActivationTokenAndQual getActivationTokenAndQual(IDocument doc, int documentOffset,
            boolean getFullQualifier, boolean handleForCalltips) {
        boolean changedForCalltip = false;
        boolean alreadyHasParams = false; //only useful if we're in a calltip
        int parOffset = -1;
        boolean isInMethodKeywordParam = false;
        int offsetForKeywordParam = -1;

        int foundCalltipOffset = -1;
        if (handleForCalltips) {
            int calltipOffset = documentOffset - 1;
            //ok, in this case, we have to check if we're just after a ( or ,
            if (calltipOffset > 0 && calltipOffset < doc.getLength()) {
                try {
                    char c = doc.getChar(calltipOffset);
                    while (Character.isWhitespace(c) && calltipOffset > 0) {
                        calltipOffset--;
                        c = doc.getChar(calltipOffset);
                    }
                    if (c == '(' || c == ',') {
                        //ok, we're just after a parenthesis or comma, so, we have to get the
                        //activation token and qualifier as if we were just before the last parenthesis
                        //(that is, if we're in a function call and not inside a list, string or dict declaration)
                        parOffset = calltipOffset;
                        calltipOffset = getBeforeParentesisCall(doc, calltipOffset);

                        if (calltipOffset != -1) {
                            documentOffset = calltipOffset;
                            changedForCalltip = true;
                            foundCalltipOffset = calculateProperCalltipOffset(doc, calltipOffset);
                        }
                    } else {
                        c = doc.getChar(calltipOffset);
                        while ((Character.isJavaIdentifierPart(c) || Character.isWhitespace(c)) && calltipOffset > 0) {
                            calltipOffset--;
                            c = doc.getChar(calltipOffset);
                        }
                        if (c == '(' || c == ',') {
                            calltipOffset = getBeforeParentesisCall(doc, calltipOffset);
                            if (calltipOffset != -1) {
                                offsetForKeywordParam = calltipOffset;
                                isInMethodKeywordParam = true;
                                foundCalltipOffset = calculateProperCalltipOffset(doc, calltipOffset);
                            }
                        }
                    }
                } catch (BadLocationException e) {
                    throw new RuntimeException(e);
                }
            }

        }

        if (parOffset != -1) {
            //ok, let's see if there's something inside the parenthesis
            try {
                char c = doc.getChar(parOffset);
                if (c == '(') { //only do it
                    parOffset++;
                    while (parOffset < doc.getLength()) {
                        c = doc.getChar(parOffset);
                        if (c == ')') {
                            break; //finished the parenthesis
                        }

                        if (!Character.isWhitespace(c)) {
                            alreadyHasParams = true;
                            break;
                        }
                        parOffset++;
                    }
                } else {
                    //we're after a comma, so, there surely is some parameter already
                    alreadyHasParams = true;
                }
            } catch (BadLocationException e) {
                throw new RuntimeException(e);
            }
        }

        Tuple<String, Integer> tupPrefix = extractActivationToken(doc, documentOffset, getFullQualifier);

        if (getFullQualifier == true) {
            //may have changed
            documentOffset = tupPrefix.o2;
        }

        String activationToken = tupPrefix.o1;
        documentOffset = documentOffset - activationToken.length() - 1;

        try {
            while (documentOffset >= 0 && documentOffset < doc.getLength() && doc.get(documentOffset, 1).equals(".")) {
                String tok = extractActivationToken(doc, documentOffset, false).o1;

                if (documentOffset == 0) {
                    break;
                }

                String c = doc.get(documentOffset - 1, 1);

                if (c.equals("]")) {
                    // consume [.*]
                    int docOff = documentOffset;
                    while (docOff > 0 && doc.get(docOff, 1).equals("[") == false) {
                        docOff -= 1;
                    }
                    // get activation token for the accessed list derivative
                    tok = extractActivationToken(doc, docOff, false).o1;

                    if (tok.length() > 0) {
                        // see handling of function call below
                        // this won't work for pure lists at the moment
                        activationToken = tok + ".__getitem__()." + activationToken;
                        documentOffset = docOff - tok.length() - 1;

                    } else {
                        // (old) fall-back handling
                        activationToken = "list." + activationToken;
                    }
                    break;

                } else if (c.equals("}")) {
                    activationToken = "dict." + activationToken;
                    break;

                } else if (c.equals("'") || c.equals("\"")) {
                    activationToken = "str." + activationToken;
                    break;

                } else if (c.equals(")")) {
                    documentOffset = eatFuncCall(doc, documentOffset - 1);
                    tok = extractActivationToken(doc, documentOffset, false).o1;
                    activationToken = tok + "()." + activationToken;
                    documentOffset = documentOffset - tok.length() - 1;

                } else if (tok.length() > 0) {
                    activationToken = tok + "." + activationToken;
                    documentOffset = documentOffset - tok.length() - 1;

                } else {
                    break;
                }

            }
        } catch (BadLocationException e) {
            Log.log("documentOffset " + documentOffset + "\n" + "theDoc.getLength() " + doc.getLength(), e);
        }

        String qualifier = "";
        //we complete on '.' and '('.
        //' ' gets globals
        //and any other char gets globals on token and templates.

        //we have to get the qualifier. e.g. bla.foo = foo is the qualifier.
        if (activationToken.indexOf('.') != -1) {
            while (endsWithSomeChar(new char[] { '.', '[' }, activationToken) == false && activationToken.length() > 0) {

                qualifier = activationToken.charAt(activationToken.length() - 1) + qualifier;
                activationToken = activationToken.substring(0, activationToken.length() - 1);
            }
        } else { //everything is a part of the qualifier.
            qualifier = activationToken.trim();
            activationToken = "";
        }
        return new ActivationTokenAndQual(activationToken, qualifier, changedForCalltip, alreadyHasParams,
                isInMethodKeywordParam, offsetForKeywordParam, foundCalltipOffset);
    }

    private static int calculateProperCalltipOffset(IDocument doc, int calltipOffset) {
        try {
            char c = doc.getChar(calltipOffset);
            while (c != '(') {
                calltipOffset++;
                c = doc.getChar(calltipOffset);
            }
            calltipOffset++; //right after the parenthesis
            return calltipOffset;
        } catch (BadLocationException e) {
        }
        return -1;
    }

    /**
     * This function will look for a the offset of a method call before the current offset
     *
     * @param doc: an IDocument, String, StringBuffer or char[]
     * @param calltipOffset the offset we should start looking for it
     * @return the offset that points the location just after the activation token and qualifier.
     *
     * @throws BadLocationException
     */
    public static int getBeforeParentesisCall(Object doc, int calltipOffset) {
        ParsingUtils parsingUtils = ParsingUtils.create(doc);
        char c = parsingUtils.charAt(calltipOffset);

        while (calltipOffset > 0 && c != '(') {
            calltipOffset--;
            c = parsingUtils.charAt(calltipOffset);
        }
        if (c == '(') {
            while (calltipOffset > 0 && Character.isWhitespace(c)) {
                calltipOffset--;
                c = parsingUtils.charAt(calltipOffset);
            }
            return calltipOffset;
        }
        return -1;
    }

    /**
     * This function gets the activation token from the document given the current cursor position.
     *
     * @param document this is the document we want info on
     * @param offset this is the cursor position
     * @param getFullQualifier if true we get the full qualifier (even if it passes the current cursor location)
     * @return a tuple with the activation token and the cursor offset (may change if we need to get the full qualifier,
     *         otherwise, it is the same offset passed as a parameter).
     */
    public static Tuple<String, Integer> extractActivationToken(IDocument document, int offset, boolean getFullQualifier) {
        try {
            if (getFullQualifier) {
                //if we have to get the full qualifier, we'll have to walk the offset (cursor) forward
                while (offset < document.getLength()) {
                    char ch = document.getChar(offset);
                    if (Character.isJavaIdentifierPart(ch)) {
                        offset++;
                    } else {
                        break;
                    }

                }
            }
            int i = offset;

            if (i > document.getLength())
                return new Tuple<String, Integer>("", document.getLength()); //$NON-NLS-1$

            while (i > 0) {
                char ch = document.getChar(i - 1);
                if (!Character.isJavaIdentifierPart(ch))
                    break;
                i--;
            }

            return new Tuple<String, Integer>(document.get(i, offset - i), offset);
        } catch (BadLocationException e) {
            return new Tuple<String, Integer>("", offset); //$NON-NLS-1$
        }
    }

    /**
     * @param c
     * @param string
     */
    public static boolean containsOnly(char c, String string) {
        for (int i = 0; i < string.length(); i++) {
            if (string.charAt(i) != c) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param string the string we care about
     * @return true if the string passed is only composed of whitespaces (or characters that
     * are regarded as whitespaces by Character.isWhitespace)
     */
    public static boolean containsOnlyWhitespaces(String string) {
        for (int i = 0; i < string.length(); i++) {
            if (Character.isWhitespace(string.charAt(i)) == false) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param selection the text from where we want to get the indentation
     * @return a string representing the whitespaces and tabs befor the first char in the passed line.
     */
    public static String getIndentationFromLine(String selection) {
        int firstCharPosition = getFirstCharPosition(selection);
        return selection.substring(0, firstCharPosition);
    }

    public String getIndentationFromLine() {
        return getIndentationFromLine(getCursorLineContents());
    }

    /**
     * @param doc
     * @param region
     * @return
     * @throws BadLocationException
     */
    public static int getFirstCharRelativePosition(IDocument doc, IRegion region) throws BadLocationException {
        int offset = region.getOffset();
        String src = doc.get(offset, region.getLength());

        return getFirstCharPosition(src);
    }

    /**
     * @param doc
     * @param cursorOffset
     * @return
     * @throws BadLocationException
     */
    public static int getFirstCharRelativeLinePosition(IDocument doc, int line) throws BadLocationException {
        IRegion region;
        region = doc.getLineInformation(line);
        return getFirstCharRelativePosition(doc, region);
    }

    /**
     * @param doc
     * @param cursorOffset
     * @return
     * @throws BadLocationException
     */
    public static int getFirstCharRelativePosition(IDocument doc, int cursorOffset) throws BadLocationException {
        IRegion region;
        region = doc.getLineInformationOfOffset(cursorOffset);
        return getFirstCharRelativePosition(doc, region);
    }

    /**
     * Returns the position of the first non whitespace char in the current line.
     * @param doc
     * @param cursorOffset
     * @return position of the first character of the line (returned as an absolute
     *            offset)
     * @throws BadLocationException
     */
    public static int getFirstCharPosition(IDocument doc, int cursorOffset) throws BadLocationException {
        IRegion region;
        region = doc.getLineInformationOfOffset(cursorOffset);
        int offset = region.getOffset();
        return offset + getFirstCharRelativePosition(doc, cursorOffset);
    }

    /**
     * @return true if this line starts with a dedent token (the passed string should be already trimmed)
     */
    public static boolean startsWithDedentToken(String trimmedLine) {
        for (String dedent : PySelection.DEDENT_TOKENS) {
            if (trimmedLine.startsWith(dedent)) {
                return isCompleteToken(trimmedLine, dedent);
            }
        }
        return false;
    }

    /**
     * @return true if this line starts with an indent token (the passed string should be already trimmed)
     */
    public static boolean startsWithIndentToken(String trimmedLine) {
        for (String dedent : PySelection.INDENT_TOKENS) {
            if (trimmedLine.startsWith(dedent)) {
                return isCompleteToken(trimmedLine, dedent);
            }
        }
        return false;
    }

    private static boolean isCompleteToken(String trimmedLine, String dedent) {
        if (dedent.length() < trimmedLine.length()) {
            char afterToken = trimmedLine.charAt(dedent.length());
            if (afterToken == ' ' || afterToken == ':' || afterToken == ';' || afterToken == '(') {
                return true;
            }
            return false;
        } else {
            return true;
        }
    }

    /**
     * Class to help iterating through the document
     */
    public static class DocIterator implements Iterator<String> {
        private int startingLine;
        private boolean forward;
        private boolean isFirst = true;
        private int numberOfLines;
        private int lastReturnedLine = -1;
        private PySelection ps;

        public DocIterator(boolean forward, PySelection ps) {
            this(forward, ps, ps.getCursorLine(), true);
        }

        public DocIterator(boolean forward, PySelection ps, int startingLine, boolean considerFirst) {
            this.startingLine = startingLine;
            this.forward = forward;
            numberOfLines = ps.getDoc().getNumberOfLines();
            this.ps = ps;
            if (!considerFirst) {
                isFirst = false;
            }
        }

        public int getCurrentLine() {
            return startingLine;
        }

        public boolean hasNext() {
            if (forward) {
                return startingLine < numberOfLines;
            } else {
                return startingLine >= 0;
            }
        }

        /**
         * Note that the first thing it returns is the lineContents to cursor (and only after that
         * does it return from the full line -- if it is iterating backwards).
         */
        public String next() {
            try {
                String line;
                if (forward) {
                    line = ps.getLine(startingLine);
                    lastReturnedLine = startingLine;
                    startingLine++;
                } else {
                    if (isFirst) {
                        line = ps.getLineContentsToCursor();
                        isFirst = false;
                    } else {
                        line = ps.getLine(startingLine);
                    }
                    lastReturnedLine = startingLine;
                    startingLine--;
                }
                return line;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public void remove() {
            throw new RuntimeException("Remove not implemented.");
        }

        public int getLastReturnedLine() {
            return lastReturnedLine;
        }
    }

    /**
     * @return if the offset is inside the region
     */
    public static boolean isInside(int offset, IRegion region) {
        if (offset >= region.getOffset() && offset <= (region.getOffset() + region.getLength())) {
            return true;
        }
        return false;
    }

    /**
     * @return if the col is inside the initial col/len
     */
    public static boolean isInside(int col, int initialCol, int len) {
        if (col >= initialCol && col <= (initialCol + len)) {
            return true;
        }
        return false;
    }

    /**
     * @return if the region passed is composed of a single line
     */
    public static boolean endsInSameLine(IDocument document, IRegion region) {
        try {
            int startLine = document.getLineOfOffset(region.getOffset());
            int end = region.getOffset() + region.getLength();
            int endLine = document.getLineOfOffset(end);
            return startLine == endLine;
        } catch (BadLocationException e) {
            return false;
        }
    }

    /**
     * @param offset the offset we want info on
     * @return a tuple with the line, col of the passed offset in the document
     */
    public Tuple<Integer, Integer> getLineAndCol(int offset) {
        try {
            IRegion region = doc.getLineInformationOfOffset(offset);
            int line = doc.getLineOfOffset(offset);
            int col = offset - region.getOffset();
            return new Tuple<Integer, Integer>(line, col);
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @return the contents from the document starting at the cursor line until a colon is reached.
     */
    public String getToColon() {
        FastStringBuffer buffer = new FastStringBuffer();

        int docLen = doc.getLength();
        for (int i = getLineOffset(); i < docLen; i++) {
            try {
                char c = doc.getChar(i);
                buffer.append(c);
                if (c == ':') {
                    return buffer.toString();
                }
            } catch (BadLocationException e) {
                throw new RuntimeException(e);
            }
        }
        return ""; //unable to find a colon
    }

    /**
     * @param matchOnlyComplete if true matches only if a complete signature is found. If false,
     * matches even if only the 'def' and name are available.
     */
    public boolean isInFunctionLine(boolean matchOnlyComplete) {
        String line;
        if (!matchOnlyComplete) {
            //does not requires colon
            line = this.getLine();
        } else {
            //requires colon
            line = getToColon();
        }
        return matchesFunctionLine(line);
    }

    public static boolean matchesFunctionLine(String line) {
        return FunctionPattern.matcher(line.trim()).matches();
    }

    public static boolean isIdentifier(String str) {
        return IdentifierPattern.matcher(str).matches();
    }

    public boolean isInClassLine() {
        String line = this.getLine().trim();
        return matchesClassLine(line);
    }

    public static boolean matchesClassLine(String line) {
        return ClassPattern.matcher(line).matches();
    }

    //spaces* 'def' space+ identifier space* ( (space|char|.|,|=|*|(|)|'|")* ):
    private static final Pattern FunctionPattern = Pattern.compile("\\s*def\\s+\\w*.*", Pattern.DOTALL);

    //spaces* 'class' space+ identifier space* (? (.|char|space |,)* )?
    private static final Pattern ClassPattern = Pattern.compile("\\s*class\\s+\\w*.*", Pattern.DOTALL);

    private static final Pattern IdentifierPattern = Pattern.compile("\\w*");

    public static boolean isCommentLine(String line) {
        for (int j = 0; j < line.length(); j++) {
            char c = line.charAt(j);
            if (c == '#') {
                //ok, it starts with # (so, it is a comment)
                return true;
            } else if (!Character.isWhitespace(c)) {
                return false;
            }
        }
        return false;
    }

    public static int DECLARATION_NONE = 0;
    public static int DECLARATION_CLASS = 1;
    public static int DECLARATION_METHOD = 2;

    /**
     * @return whether the current selection is on the ClassName or Function name context
     * (just after the 'class' or 'def' tokens)
     */
    public int isInDeclarationLine() {
        try {
            String contents = getLineContentsToCursor();
            StringTokenizer strTok = new StringTokenizer(contents);
            if (strTok.hasMoreTokens()) {
                String tok = strTok.nextToken();
                int decl = DECLARATION_NONE;
                if (tok.equals("class")) {
                    decl = DECLARATION_CLASS;
                } else if (tok.equals("def")) {
                    decl = DECLARATION_METHOD;
                }
                if (decl != DECLARATION_NONE) {

                    //ok, we're in a class or def line... so, if we find a '(' or ':', we're not in the declaration...
                    //(otherwise, we're in it)
                    while (strTok.hasMoreTokens()) {
                        tok = strTok.nextToken();
                        if (tok.indexOf('(') != -1 || tok.indexOf(':') != -1) {
                            return DECLARATION_NONE;
                        }
                    }
                    return decl;
                }
            }
        } catch (BadLocationException e) {
        }
        return DECLARATION_NONE;
    }

    public IRegion getRegion() {
        return new Region(this.textSelection.getOffset(), this.textSelection.getLength());
    }

    public int getEndOfDocummentOffset() {
        int length = this.doc.getLength();
        return length;
    }

    /**
     * @param currentOffset the current offset should be at the '(' or at a space before it (if we are at any other
     * char, this method will always return an empty list).
     */
    public List<String> getParametersAfterCall(int currentOffset) {
        try {
            currentOffset -= 1;
            char c;
            do {
                currentOffset += 1;
                c = doc.getChar(currentOffset);
            } while (Character.isWhitespace(c));

            if (c == '(') {
                Tuple<List<String>, Integer> insideParentesisToks = getInsideParentesisToks(true, currentOffset, true);
                return insideParentesisToks.o1;
            }

        } catch (Exception e) {
            //ignore any problem getting parameters here
        }

        return new ArrayList<String>();
    }

    private static final Pattern ClassNamePattern = Pattern.compile("\\s*class\\s+(\\w+)");

    public static String getClassNameInLine(String line) {
        Matcher matcher = ClassNamePattern.matcher(line);
        if (matcher.find()) {
            if (matcher.groupCount() == 1) {
                return matcher.group(1);
            }
        }
        return null;
    }

    public static final class TddPossibleMatches {
        public final String full;
        public final String initialPart;
        public final String secondPart;

        public TddPossibleMatches(String full, String initialPart, String secondPart) {
            this.full = full;
            this.initialPart = initialPart;
            this.secondPart = secondPart;
        }

        @Override
        public String toString() {
            return this.full;
        }

    }

    //0 = full
    //1 = (\\.?)
    //2 = (\\w+)
    //3 = ((\\.\\w+)*)
    //4 = \\s*
    //5 = ((\\()?)
    //
    //i.e.:for a.b.MyCall(
    //0 = a.b.MyCall(
    //1 = null
    //2 = a
    //3 = .b.MyCall
    //4 = null
    //5 = (
    private static final Pattern FunctionCallPattern = Pattern.compile("(\\.?)(\\w+)((\\.\\w+)*)\\s*((\\()?)");

    public List<TddPossibleMatches> getTddPossibleMatchesAtLine() {
        return getTddPossibleMatchesAtLine(this.getAbsoluteCursorOffset());
    }

    private static final int TDD_PART_FULL = 0;
    private static final int TDD_PART_DOT_INITIAL = 1;
    private static final int TDD_PART_PART1 = 2;
    private static final int TDD_PART_PART2 = 3;
    private static final int TDD_PART_PARENS = 5;

    /**
     * @return a list
     */
    public List<TddPossibleMatches> getTddPossibleMatchesAtLine(int offset) {
        String line = getLine(getLineOfOffset(offset));
        return getTddPossibleMatchesAtLine(line);

    }

    public List<TddPossibleMatches> getTddPossibleMatchesAtLine(String line) {
        List<TddPossibleMatches> ret = new ArrayList<TddPossibleMatches>();
        if (matchesClassLine(line) || matchesFunctionLine(line)) {
            return ret;//In a class or method definition, it should never match.
        }
        Matcher matcher = FunctionCallPattern.matcher(line);
        while (matcher.find()) {
            String dotInitial = matcher.group(TDD_PART_DOT_INITIAL);
            if (dotInitial != null && dotInitial.length() > 0) {
                continue; //skip things as foo().bar() <-- the .bar() should be skipped
            }
            String secondPart = matcher.group(TDD_PART_PART2);
            String parens = matcher.group(TDD_PART_PARENS);
            boolean hasCall = parens != null && parens.length() > 0;
            if (secondPart.length() == 0 && !hasCall) {
                continue; //local var or number
            }
            ret.add(new TddPossibleMatches(matcher.group(TDD_PART_FULL), matcher.group(TDD_PART_PART1), secondPart));
        }
        return ret;
    }

    /**
     * @param offset
     * @return
     */
    public boolean intersects(int offset, int len) {
        int currOffset = this.textSelection.getOffset();
        int currLen = this.textSelection.getLength();

        ///The end is after the end of the current sel
        if (offset >= currOffset + currLen) {
            return false;
        }
        if (offset + len <= currOffset) {
            return false;
        }
        return true;
    }

}
TOP

Related Classes of org.python.pydev.core.docutils.PySelection$LineStartingScope

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
d', 'pageview');